<?php

namespace Core;

use Phalcon\Events\Event;
use Phalcon\Mvc\Dispatcher;
use Phalcon\Security as PhalconSecurity;
use Phalcon\Di;

/**
 * Class Security
 * @package Core
 * @property \Phalcon\Mvc\Dispatcher|\Phalcon\Mvc\DispatcherInterface $dispatcher
 * @property \Phalcon\Mvc\Router|\Phalcon\Mvc\RouterInterface $router
 * @property \Phalcon\Mvc\Url|\Phalcon\Mvc\UrlInterface $url
 * @property \Phalcon\Http\Request|\Phalcon\Http\RequestInterface $request
 * @property \Phalcon\Http\Response|\Phalcon\Http\ResponseInterface $response
 * @property \Phalcon\Http\Response\Cookies|\Phalcon\Http\Response\CookiesInterface $cookies
 * @property \Core\Security\User $user
 * @property \Core\Forms\Manager $form
 * @property \Phalcon\Http\Cookie $cookie
 * @property \Core\Mvc\View\Volt $volt
 * @property \Core\Filter $filter
 * @property \Phalcon\Flash\Direct $flash
 * @property \Core\Config $config
 * @property \Core\Mvc\EntityManager $entityManager
 * @property \Phalcon\Flash\Session $flashSession
 * @property \Phalcon\Session\Adapter\Files|\Phalcon\Session\Adapter|\Phalcon\Session\AdapterInterface $session
 * @property \Phalcon\Events\Manager|\Phalcon\Events\ManagerInterface $eventsManager
 * @property \Phalcon\Db\AdapterInterface $db
 * @property \Core\Security $security
 * @property \Phalcon\Crypt|\Phalcon\CryptInterface $crypt
 * @property \Phalcon\Tag $tag
 * @property \Phalcon\Escaper|\Phalcon\EscaperInterface $escaper
 * @property \Phalcon\Annotations\Adapter\Memory|\Phalcon\Annotations\Adapter $annotations
 * @property \Phalcon\Mvc\Model\Manager|\Phalcon\Mvc\Model\ManagerInterface $modelsManager
 * @property \Phalcon\Mvc\Model\MetaData\Memory|\Phalcon\Mvc\Model\MetadataInterface $modelsMetadata
 * @property \Phalcon\Mvc\Model\Transaction\Manager|\Phalcon\Mvc\Model\Transaction\ManagerInterface $transactionManager
 * @property \Core\Assets\Manager $assets
 * @property \Phalcon\Di|\Phalcon\DiInterface $di
 * @property \Phalcon\Session\Bag|\Phalcon\Session\BagInterface $persistent
 * @property \Core\Mvc\View $view
 */
class Security extends PhalconSecurity
{

    /**
     * @var
     */
    public $securityHook;
    /**
     * @var
     */
    public $security;
    /**
     * @var
     */
    public $controllerName;
    /**
     * @var
     */
    public $actionName;
    /**
     * @var
     */
    public $module;
    /**
     * @var
     */
    public $securityName;
    /**
     * @var
     */
    public $params;
    /**
     * @var
     */
    public $roles;
    /**
     * @var
     */
    protected $uid;
    /**
     * @var bool
     */
    public $adopt = false;


    public function getAcl()
    {
        $di = Di::getDefault();
        $this->security = $di->getShared('config')->get('security');
        $this->securityHook = $di->getShared('config')->cache('securityHook');
        $this->roles = $di->getShared('user')->getRoles();
        $this->uid = $di->getShared('user')->getUid();
        $this->controllerName = strtolower($di->getShared('dispatcher')->getControllerName());
        $this->actionName = strtolower($di->getShared('dispatcher')->getActionName());
        $this->module = strtolower($di->getShared('router')->getModuleName());
        $this->params = $di->getShared('dispatcher')->getParams();
        $this->securityName = array(
            $this->controllerName,
            $this->module . ':' . $this->controllerName,
            $this->module . ':' . $this->controllerName . ':' . $this->actionName,
        );
        $end = $this->module . ':' . $this->controllerName . ':' . $this->actionName;
        foreach ($this->params as $value) {
            $end = $end . '-' . $value;
            $this->securityName[] = $end;
        }
        $this->securityName = array_reverse($this->securityName);
    }

    /**
     * @param $access
     * @return bool
     */
    public function access($access)
    {
        $di = Di::getDefault();
        $url = $di->getShared('request')->getURI();
        if (isset($access['path']) && is_array($access['path']) && !empty($access['path'])) {
            $output = false;
            foreach ($access['path'] as $path) {
                if (preg_match(stripslashes($path), $url)) {
                    break;
                }
                if ($output === false) {
                    return false;
                }
            }
        }
        if (isset($access['expath']) && is_array($access['expath']) && !empty($access['expath'])) {
            foreach ($access['expath'] as $path) {
                if (preg_match(stripslashes($path), $url)) {
                    return false;
                }
            }
        }
        if (isset($access['roles']) && is_array($access['roles']) && !empty($access['roles'])) {
            if (empty(array_intersect($this->roles, $access['roles']))) {
                return false;
            }
        }
        if (isset($access['exroles']) && is_array($access['exroles']) && !empty($access['exroles'])) {
            if (count(array_intersect($this->roles, $access['exroles'])) == count($this->roles)) {
                return false;
            }
        }
        if (isset($access['user']) && is_array($access['user']) && !empty($access['user'])) {
            if (is_string($access['user'])) {
                $access['user'] = explode(',', $access['user']);
            }
            if (!array_search($this->uid, $access['user'])) {
                return false;
            }
        }
        if (isset($access['exuser']) && is_array($access['exuser']) && !empty($access['exuser'])) {
            if (is_string($access['exuser'])) {
                $access['exuser'] = explode(',', $access['exuser']);
            }
            if (array_search($this->uid, $access['exuser'])) {
                return false;
            }
        }
        if (isset($access['custom']) && is_array($access['custom']) && !empty($access['custom'])) {
            foreach ($access['custom'] as $custom) {
                if (isset($custom['callable']) && is_callable($custom['callable'])) {
                    if (call_user_func($custom['callable'], $custom) === false) {
                        return false;
                    }
                }
            }
        }
        return true;
    }

    /**
     * @param $url
     */
    public function isUrlCanAccess($url)
    {

    }

    /**
     * @param Event $event
     * @param Dispatcher $dispatcher
     * @return bool
     */
    public function beforeDispatch(Event $event, Dispatcher $dispatcher)
    {
        $this->getAcl();
        $di = Di::getDefault();
        foreach ($this->securityName as $sn) {
            foreach ($this->roles as $role => $state) {
                if (isset($this->security[$sn][$role])) {
                    if ($this->security[$sn][$role] === true) {
                        $this->adopt = true;
                        break 2;
                    } elseif ($this->security[$sn][$role] === false) {
                        $this->adopt = false;
                        break 2;
                    }
                    if (isset($this->securityHook[$sn])) {
                        $this->adopt = call_user_func($sn, $this);
                        if ($this->adopt === true) {
                            break 2;
                        } elseif ($this->adopt === false) {
                            break 2;
                        }
                    }
                }
            }
        }
        if ($this->adopt == false) {
            $di->getShared('response')->setStatusCode(404, "Not Found");
            $di->getShared('dispatcher')->forward(array(
                'controller' => 'Index',
                'action' => 'NotFound',
                'module' => 'core',
                'namespace' => 'Modules\Core\Controllers',
            ));
            return false;
        } else {
            return true;
        }
    }
}
